package cn.kinoko.interceptors;

import cn.kinoko.common.base.model.Payload;
import cn.kinoko.common.base.model.Result;
import cn.kinoko.common.code.ErrorCodeEnum;
import cn.kinoko.common.constant.FileConstant;
import cn.kinoko.common.constant.HeaderConstant;
import cn.kinoko.common.utils.ExceptionUtil;
import cn.kinoko.common.utils.JwtUtils;
import cn.kinoko.common.utils.RSAUtil;
import cn.kinoko.common.utils.ThreadLocalUtil;
import cn.kinoko.model.auth.vo.PayloadUserVO;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;

/**
 * @author kinoko
 */
@Slf4j
public class TokenInterceptor implements HandlerInterceptor {

    private final static String[] EXCLUDE_PATH = {
            "/auth",
            "/manga/homePage",
            "/manga/heatRanking",
            "/manga/updateRanking",
            "/manga/searchManga",
            "/manga/ep/cookedList",
            "/manga/plusView",
            "/manga/epList/",
            "/manga/info/",
            "/manga/likeCount"
    };

    @Override
    public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler) throws Exception {
        String path = request.getRequestURI();
        // 预防越权攻击，如/..auth/
        if (path.contains(".")) {
            log.error("[鉴权网关]：捕获越权攻击 = {}", path);
            // 响应授权失败，不放行
            return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0301);
        }
        // 判断是否持有token以及token是否有效
        String token = request.getHeader(HeaderConstant.TOKEN_HEADER);
        try {
            // 未持有token
            if (StringUtils.isEmpty(token)) {
                // 校验是否为放行路径
                boolean inExclude = Arrays.stream(EXCLUDE_PATH).anyMatch(excludePath -> path.startsWith(excludePath) && !path.startsWith("/auth/refresh"));
                // 否则拦截
                if (!inExclude) {
                    log.error("[鉴权网关]：缺少token = {}", path);
                    // 响应授权失败，不放行
                    return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0230);
                } else {
                    return true;
                }
            } else {
                // 持有token直接解析token
                Payload<PayloadUserVO> infoFromToken = JwtUtils.getInfoFromToken(token, RSAUtil.readPublicKey(FileConstant.PUB_KEY), PayloadUserVO.class);
                // 取出uid放入请求头中，方便后续过滤器使用
                Long uid = infoFromToken.getInfo().getUid();
                ExceptionUtil.isNull(uid, ErrorCodeEnum.USER_ERROR_A0340);
                // 将用户id放入ThreadLocal
                ThreadLocalUtil.put(HeaderConstant.UID_HEADER, uid);
                // 放行
                return true;
            }
        } catch (ExpiredJwtException e) {
            // SignatureException 验签失败 | ExpiredJwtException 令牌过期
            // 响应授权失败，不放行
            return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0311);
        } catch (Exception e) {
            log.error("[鉴权网关]：验签异常 = {};path = {}", e.getMessage(), path);
            return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0342);
        }
    }

    /**
     * 错误响应
     *
     * @param response  响应对象
     * @param errorCode 错误码
     */
    private static boolean errorResponse(HttpServletResponse response, ErrorCodeEnum errorCode) throws IOException {
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=utf-8");
        // 写入错误信息
        byte[] jsonBytes = JSON.toJSONBytes(Result.error(errorCode));
        response.getOutputStream().write(jsonBytes);
        response.getOutputStream().flush();
        response.getOutputStream().close();
        return false;
    }

    @Override
    public void afterCompletion(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull Object handler, Exception ex) {
        // 清除ThreadLocal，防止内存泄漏
        ThreadLocalUtil.remove(HeaderConstant.UID_HEADER);
    }
}
